package epam.concurrency.lab2.task1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Multi-threaded Sin*Cos Summer with java.util.concurrent
 * @author Iuriy Miedviediev
 * @version 3.0 Update 14.08.2014
 */
public class SinCosRangeAccumulator {

    public static final double STEP = 0.0001;
    public static final int FACTOR = 2;
    public static final int ROUNDTO = 10000;
    private static final Logger LOGGER = LoggerFactory.getLogger(SinCosRangeAccumulator.class);

    private SinCosRangeAccumulator() {
    }

    /**
     * Sum up all the sin*cos within the range [-range, range]
     * @param range indicator of the range
     * @param threads number of desirable threads
     * @return sum of all the sin*cos within given range
     */
    public static double countSin(int range, int threads) {

        ExecutorService es = Executors.newFixedThreadPool(threads);

        //variable to accumulate results from workers
        double goal = 0.0;

        //lower bound of the range
        double begin = (double)- range;

        //round number to 0.0001 to prevent infinite numbers
        double numForThread = customRound((range*FACTOR+1) / (double)threads);

        Future<Double>[] workers = new Future[threads];

        for(int thread = 0; thread < threads; thread++) {

            //upper bound for the thread
            double maxForThread = customRound(begin + numForThread);

            //for the last thread upper bound is the upper bound of the whole range
            if(thread == threads - 1 || maxForThread >= range) {
                maxForThread = (double)range;
            }

            //start a new worker
            workers[thread] = es.submit(new SinCosSummer(begin, maxForThread));

            //correct lower bound for the next thread
            begin = customRound(maxForThread + STEP);
        }

        //wait for workers to finish and collect results
        for(Future<Double> f : workers) {
            try {
                goal += f.get();
            } catch (InterruptedException | ExecutionException e) {
                LOGGER.error("Failed to collect results", e);
            }
        }
        return goal;
    }

    /**
     * Round given number to 0.0001
     * @param number number to round
     * @return number rounded to 0.0001
     */
    public static double customRound(double number) {
        return (double) Math.round(number * ROUNDTO) / ROUNDTO;
    }
}
